using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Input;

namespace OrigPacman
{
    /// <summary>
    /// This class represents a pacman character.
    /// </summary>
    public class Pacman: Character
    {
        #region Variables
        // pacman's current position on the map
        protected Vector2 position;
        // the texture for pacman's graphic
        protected Texture2D texture;
        // the speed that pacman moves at
        protected int speed;
        protected Direction currentDirection;
        protected Rectangle container;
        protected List<Texture2D> textures;
        // the width of the rectangle around pacman
        protected int pacmanWidth = 32;
        protected List<Rectangle> frames;
        protected List<Rectangle> deathFrames;
        protected int frameIndex;
        protected int lives;
        protected bool alive;
        protected int score;
        // the animation for when pacman dies
        protected Texture2D deathAnim;
        // tells if pacman is in super mode
        protected bool super;
        protected SoundBank sounds;
        // time left in super-pacman mode
        protected float superTime;

        protected Game1 currentGame;
        protected bool playDeathAnimation = false;
        protected bool adjustDistance = false;
        protected float length = 0.0f;
        protected int nextWayPoint = 0;
        protected float totalTime = 0.0f;
        //Saves the current Key information
        protected Keys currentKey;
        protected int currentX = 0;
        protected float frameTime = DefaultFrameTime;
        protected int currentDeathFrame = 0;
        protected int prevWayPoint = 0;
        protected int ghostPoints;
        #endregion

        #region constants
        // the default number of lives pacman gets
        public const int MaxNumOfLives = 3;
        /// the distance pacman moves in one time step
        public const float DistPerTimeStep = 65.0f;
        public const float DefaultFrameTime = 0.25f;
        // the number of frames made up in the pacman animation
        public const int NumOfPacmanFrames = 3;
        public const int NumOfDeathFrames = 4;
        public const int PointsPerDeadGhost = 200;
        #endregion

        #region Properties
        public float SuperTime
        {
            get { return superTime; }
            set 
            { 
                superTime = value;
                if (superTime <= 0)
                {
                    super = false;
                }
            }
        }
        public SoundBank Sounds
        {
            get { return sounds; }
            set 
            { 
                sounds = value;
            }
        }
        public bool Super
        {
            get { return super; }
            set { super = value; }
        }
        public Texture2D Texture
        {
            get { return texture; }
            set
            {
                texture = value;
            }
        }
        public Texture2D DeathAnimation
        {
            get { return deathAnim; }
            set
            {
                deathAnim = value;
            }
        }
        public List<Texture2D> Textures
        {
            get { return textures; }
            set
            {
                textures = value;
            }
        }
        public Vector2 Position
        {
            get { return position; }
            set
            {
                position = value;
                container.X = (int)position.X;
                container.Y = (int)position.Y;
            }
        }
        public Direction CurrentDirection
        {
            get { return currentDirection; }
            set 
            { 
                currentDirection = value;
                if (textures.Count > (int)value)
                {
                    texture = textures[(int)value];
                }
            }
        }
        public int Speed
        {
            get { return speed; }
            set { speed = value; }
        }
        public int NextWayPoint
        {
            get { return nextWayPoint; }
            set { nextWayPoint = value; }
        }
        public int PreviousWayPoint
        {
            get { return prevWayPoint; }
            set { prevWayPoint = value; }
        }
        public Rectangle Bounds
        {
            get { return container; }
            set
            {
                container = value;
                position = new Vector2(container.X, container.Y);
            }
        }
        /// <summary>
        /// Get or set the width of pacman's rectangle.
        /// </summary>
        public int Size
        {
            get { return pacmanWidth; }
            set
            {
                pacmanWidth = value;
                container.Width = pacmanWidth;
                container.Height = pacmanWidth;
            }
        }
        public Rectangle CurrentFrame
        {
            get { return frames[frameIndex]; }
            set
            {
                if (frames.Contains(value))
                {
                    frameIndex = frames.IndexOf(value);
                }
            }
        }
        public int FrameIndex
        {
            get
            {
                return frameIndex;
            }
            set
            {
                frameIndex = value;
                if (frameIndex > 2)
                {
                    frameIndex = 0;
                }
            }
        }
        public int Lives
        {
            get { return lives; }
            set 
            { 
                lives = value;
                if (lives <= 0)
                {
                    alive = false;
                }
            }
        }
        public bool Alive
        {
            get { return alive; }
            set { alive = value; }
        }
        public int Score
        {
            get { return score; }
            set { score = value; }
        }
        public Keys CurrentKey
        {
            get { return currentKey; }
            set { currentKey = value; }
        }
        #endregion

        /// <summary>
        /// Creates a new pacman character.
        /// </summary>
        /// <param name="currentGame">the main game class</param>
        public Pacman(Game1 currentGame)
        {
            superTime = 0.0f;
            lives = MaxNumOfLives;
            alive = true;
            super = false;
            textures = new List<Texture2D>();
            container = new Rectangle(0,0,pacmanWidth, pacmanWidth);
            frames = new List<Rectangle>();
            deathFrames = new List<Rectangle>();
            frameIndex = 0;
            currentDeathFrame = 0;
            for (int i = 0; i < NumOfPacmanFrames; i++)
            {
                frames.Add(new Rectangle(pacmanWidth * i, 0, pacmanWidth, pacmanWidth));
            }

            for (int i = 0; i < NumOfDeathFrames; i++)
            {
                deathFrames.Add(new Rectangle(pacmanWidth * i, 0, pacmanWidth, pacmanWidth));
            }

            this.currentGame = currentGame;
        }

        #region Pacman methods
        /// <summary>
        /// Handles all of the updating for pacman,
        /// such as his position, checking for collisions,
        /// and handling the super-pacman mode specifics.
        /// </summary>
        /// <param name="elapsedTime">the time that has gone by</param>
        public void UpdatePacman(float elapsedTime)
        {
            if (Alive)
            {
                CheckKeys();
                if (!playDeathAnimation)
                {
                    UpdateSuper(elapsedTime);

                    CheckCollisionWithGhosts();

                    UpdatePosition(elapsedTime);

                    totalTime += elapsedTime;
                    if (totalTime > DefaultFrameTime)
                    {
                        currentKey = Keys.R;
                        currentX += pacmanWidth;
                        totalTime = 0.0f;
                        if (currentX > 2 * pacmanWidth)
                            currentX = 0;
                    }

                    CheckNextWaypoint();
                }
                    
                //Update animation time
                frameTime -= elapsedTime;
            }
        }

        /// <summary>
        /// If pacman has eaten a super pelet,
        /// make the ghosts run away from him until
        /// the super power has worn off
        /// </summary>
        /// <param name="elapsed"></param>
        private void UpdateSuper(float elapsedTime)
        {
            if (Super)
            {
                foreach (Ghost g in currentGame.GhostList)
                {
                    g.CurrentState = Ghost.State.RUN;
                }
            }

            if (SuperTime > 0.0f)
            {
                SuperTime -= elapsedTime;
            }

            /// the super time has ended, so make the ghosts
            /// chase pacman again
            if (SuperTime <= 0.0f)
            {
                foreach (Ghost ghost in currentGame.GhostList)
                {
                    if (!ghost.Alive)
                    {
                        ghost.Alive = true;
                    }
                    ghost.CurrentState = Ghost.State.ATTACK;
                }
            }
        }

        /// <summary>
        /// Update's position depending on which way he is heading.
        /// </summary>
        /// <param name="elapsed">the time elapsed in the game</param>
        private void UpdatePosition(float elapsedTime)
        {
            switch (CurrentDirection)
            {
                case Direction.NORTH:
                    Position = new Vector2(Position.X, Position.Y - DistPerTimeStep * elapsedTime);
                    Texture = Textures[(int)Direction.NORTH];
                    if (adjustDistance)
                    {
                        Position = new Vector2(Position.X, Position.Y - length);
                        adjustDistance = false;
                    }

                    break;

                case Direction.SOUTH:
                    Position = new Vector2(Position.X, Position.Y + DistPerTimeStep * elapsedTime);
                    Texture = Textures[(int)Direction.SOUTH];
                    if (adjustDistance)
                    {
                        Position = new Vector2(Position.X, Position.Y + length);
                        adjustDistance = false;
                    }
                    break;

                case Direction.WEST:
                    Position = new Vector2(Position.X - DistPerTimeStep * elapsedTime, Position.Y);
                    Texture = Textures[(int)Direction.WEST];
                    if (adjustDistance)
                    {
                        Position = new Vector2(Position.X - length, Position.Y);
                        adjustDistance = false;
                    }
                    break;

                case Direction.EAST:
                    Position = new Vector2(Position.X + DistPerTimeStep * elapsedTime, Position.Y);
                    Texture = Textures[(int)Direction.EAST];
                    if (adjustDistance)
                    {
                        Position = new Vector2(Position.X + length, Position.Y);
                        adjustDistance = false;
                    }
                    break;

                case Direction.STOP:
                    //Do nothing
                    Waypoint theNextWaypoint = currentGame.Waypoints[nextWayPoint];
                    Position = theNextWaypoint.Position;
                    break;
            }
        }

        /// <summary>
        /// Draw pacman to the screen.
        /// </summary>
        /// <param name="sprite">the main sprite batch</param>
        public void RenderPacman(ref SpriteBatch sprite)
        {
            if (Alive)
            {
                if (!playDeathAnimation)
                {
                    if (frameTime < 0.0f && FrameIndex < NumOfPacmanFrames)
                    {
                        frameTime = DefaultFrameTime;
                        FrameIndex++;
                    }

                    if (Super)
                    {
                        sprite.Draw(Texture, Bounds, CurrentFrame, Color.OrangeRed);
                    }
                    else
                    {
                        sprite.Draw(Texture, Bounds, CurrentFrame, Color.White);
                    }
                }
                else
                {
                    PlayDeathAnimation(ref sprite);
                }
            }
        }

        /// <summary>
        /// Displays a little animation sequence when pacman dies.
        /// </summary>
        /// <param name="sprite">the main sprite batch</param>
        private void PlayDeathAnimation(ref SpriteBatch sprite)
        {
            //PlayAnimation
            if (frameTime < 0.0f && currentDeathFrame < NumOfPacmanFrames)
            {
                frameTime = DefaultFrameTime;
                FrameIndex++;
                currentDeathFrame++;
            }
            sprite.Draw(deathAnim, Bounds, deathFrames[currentDeathFrame], Color.White);
            if (currentDeathFrame == NumOfPacmanFrames && frameTime < 0.0f)
            {
                frameTime = DefaultFrameTime;
                currentDeathFrame = 0;
                playDeathAnimation = false;
                Waypoint pacmanStartingPoint = currentGame.Waypoints[0];
                Position = pacmanStartingPoint.Position;
                nextWayPoint = pacmanStartingPoint.West;
                prevWayPoint = pacmanStartingPoint.East;
                CurrentDirection = Direction.WEST;
                if (!Alive)
                {
                    currentGame.EndGame();
                }

            }
        }

        /// <summary>
        /// Checks Key Presses when Pacman has not yet arrived to a junction
        /// </summary>
        public void CheckKeys()
        {
            if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Up) &&
                CurrentDirection == Direction.SOUTH)
            {
                int currentNextWaypoint = nextWayPoint;
                nextWayPoint = prevWayPoint;
                prevWayPoint = currentNextWaypoint;
                CurrentDirection = Direction.NORTH;
            }
            else if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Down) &&
                CurrentDirection == Direction.NORTH)
            {
                int currentNextWaypoint = nextWayPoint;
                nextWayPoint = prevWayPoint;
                prevWayPoint = currentNextWaypoint;
                CurrentDirection = Direction.SOUTH;
            }
            else if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Left) &&
                CurrentDirection == Direction.EAST)
            {
                int currentNextWaypoint = nextWayPoint;
                nextWayPoint = prevWayPoint;
                prevWayPoint = currentNextWaypoint;
                CurrentDirection = Direction.WEST;
            }
            else if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Right) &&
                CurrentDirection == Direction.WEST)
            {
                int currentNextWaypoint = nextWayPoint;
                nextWayPoint = prevWayPoint;
                prevWayPoint = currentNextWaypoint;
                CurrentDirection = Direction.EAST;
            }
            Keys tmpKey = currentKey;
            currentKey = GetKeyPress();
            if (currentKey == Keys.R)
                currentKey = tmpKey;
        }

        /// <summary>
        /// Checks to see if the next waypoint is still valid.
        /// </summary>
        private void CheckNextWaypoint()
        {
            Waypoint theNextWaypoint = currentGame.Waypoints[nextWayPoint];
            Vector2 distance = (theNextWaypoint.Position - new Vector2(Position.X, Position.Y));
            length = distance.Length();
            if (GetDirectionToWaypoint(distance) == CurrentDirection &&
                CurrentDirection != Direction.STOP)
            {
                adjustDistance = false;
                return;
            }
            else
            {
                UpdateNextWaypoint(theNextWaypoint);
            }
        }

        /// <summary>
        /// Changes the current waypoint to the new waypoint we're near.
        /// </summary>
        public void UpdateNextWaypoint(Waypoint theNextWaypoint)

        {
            adjustDistance = true;

            switch (currentKey)
            {
                case Keys.Up:
                    if (theNextWaypoint.North < 90)
                    {
                        int currentNextWaypoint = nextWayPoint;
                        nextWayPoint = theNextWaypoint.North;
                        prevWayPoint = currentNextWaypoint;
                        CurrentDirection = Direction.NORTH;
                    }
                    else
                        CurrentDirection = Direction.STOP;
                    break;
                case Keys.Down:
                    if (theNextWaypoint.South < 90)
                    {
                        int currentNextWaypoint = nextWayPoint;
                        nextWayPoint = theNextWaypoint.South;
                        prevWayPoint = currentNextWaypoint;
                        CurrentDirection = Direction.SOUTH;
                    }
                    else
                        CurrentDirection = Direction.STOP;
                    break;
                case Keys.Left:
                    if (theNextWaypoint.West < 90)
                    {
                        int currentNextWaypoint = nextWayPoint;
                        nextWayPoint = theNextWaypoint.West;
                        prevWayPoint = currentNextWaypoint;
                        CurrentDirection = Direction.WEST;
                    }
                    else
                        CurrentDirection = Direction.STOP;
                    break;
                case Keys.Right:
                    if (theNextWaypoint.East < 90)
                    {
                        int currentNextWaypoint = nextWayPoint;
                        nextWayPoint = theNextWaypoint.East;
                        prevWayPoint = currentNextWaypoint;
                        CurrentDirection = Direction.EAST;
                    }
                    else
                        CurrentDirection = Direction.STOP;
                    break;
                default:
                    switch (CurrentDirection)
                    {
                        case Direction.WEST:
                            if (theNextWaypoint.West < 99)
                            {
                                int currentNextWaypoint = nextWayPoint;
                                nextWayPoint = theNextWaypoint.West;
                                prevWayPoint = currentNextWaypoint;
                            }
                            else
                                CurrentDirection = Direction.STOP;
                            break;
                        case Direction.NORTH:
                            if (theNextWaypoint.North < 99)
                            {
                                int currentNextWaypoint = nextWayPoint;
                                nextWayPoint = theNextWaypoint.North;
                                prevWayPoint = currentNextWaypoint;
                            }
                            else
                                CurrentDirection = Direction.STOP;
                            break;
                        case Direction.EAST:
                            if (theNextWaypoint.East < 99)
                            {
                                int currentNextWaypoint = nextWayPoint;
                                nextWayPoint = theNextWaypoint.East;
                                prevWayPoint = currentNextWaypoint;
                            }
                            else
                                CurrentDirection = Direction.STOP;
                            break;
                        case Direction.SOUTH:
                            if (theNextWaypoint.South < 99)
                            {
                                int currentNextWaypoint = nextWayPoint;
                                nextWayPoint = theNextWaypoint.South;
                                prevWayPoint = currentNextWaypoint;
                            }
                            else
                                CurrentDirection = Direction.STOP;
                            break;
                    }//End Switch
                    break;
            }//End switch;
        }

        /// <summary>
        /// At the beginning of the game, this sets
        /// the waypoint for pacman's initial position
        /// </summary>
        public void SetStartingPacmanWaypoints(List<Waypoint> listOfWaypoints)
        {
            currentGame.Waypoints = listOfWaypoints;
            if (currentGame.Waypoints != null)
            {
                Waypoint DefaultPacmanWaypoint = (Waypoint)listOfWaypoints[0];
                Position = DefaultPacmanWaypoint.Position;
                nextWayPoint = DefaultPacmanWaypoint.West;
                prevWayPoint = DefaultPacmanWaypoint.East;
                CurrentDirection = Direction.WEST;
            }
        }

        /// <summary>
        /// Finds what key was pressed during this frame.
        /// </summary>
        /// <returns>the key that was pressed</returns>
        public Keys GetKeyPress()
        {
            if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Up))
                return Keys.Up;
            if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Down))
                return Keys.Down;
            if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Left))
                return Keys.Left;
            if (KeyPress.KeyPressGame.KeyPressedThisFrame(Keys.Right))
                return Keys.Right;
            return Keys.R;
        }

        /// <summary>
        /// Figures out the direction to the next waypoint based
        /// on the current distance from the next waypoint.
        /// </summary>
        /// <param name="distanceFromWaypoint">distance from the next waypoint</param>
        /// <returns>the direction to the next waypoint</returns>
        private Direction GetDirectionToWaypoint(Vector2 distanceFromWaypoint)
        {
            distanceFromWaypoint.Normalize();
            if (distanceFromWaypoint.X < 0.45f && distanceFromWaypoint.X > -0.45f)
            {
                distanceFromWaypoint.X = 0.0f;
            } else if (distanceFromWaypoint.X < -0.5f)
            {
                distanceFromWaypoint.X = -1.0f;
                distanceFromWaypoint.Y = 0.0f;
            } else if (distanceFromWaypoint.X > 0.5)
            {
                distanceFromWaypoint.X = 1.0f;
                distanceFromWaypoint.Y = 0.0f;
            }
            if (distanceFromWaypoint.Y < 0.45f && distanceFromWaypoint.Y > -0.45f)
            {
                distanceFromWaypoint.Y = 0.0f;
            } else if (distanceFromWaypoint.Y > 0.5)
            {
                distanceFromWaypoint.Y = 1.0f;
                distanceFromWaypoint.X = 0.0f;
            } else if (distanceFromWaypoint.Y < -0.5f)
            {
                distanceFromWaypoint.Y = -1.0f;
                distanceFromWaypoint.X = 0.0f;
            }
            if (distanceFromWaypoint.X == 1.0f)
                return Direction.EAST;
            else if (distanceFromWaypoint.X == -1.0f)
                return Direction.WEST;
            else if (distanceFromWaypoint.Y == 1.0f)
                return Direction.SOUTH;
            else if (distanceFromWaypoint.Y == -1.0f)
                return Direction.NORTH;
            return Direction.STOP;
        }

        /// <summary>
        /// Checks if pacman has collided with a ghost.
        /// </summary>
        private void CheckCollisionWithGhosts()
        {
            foreach (Ghost ghost in currentGame.GhostList)
            {
                //Calculate the distance to the ghost
                if (ghost.Bounds.Intersects(Bounds))
                {
                    if (ghost.CurrentState != Ghost.State.DEAD)
                    {
                        if (Super)
                        {
                            ghost.Alive = false;
                            ghost.CurrentState = Ghost.State.DEAD;
                            Score += ghostPoints;
                            Sounds.PlayCue("ghostEaten");
                            SetGhostPoints();
                            break;
                        }
                        else
                        {
                            Sounds.PlayCue("die");
                            //Collision
                            playDeathAnimation = true;
                            currentDeathFrame = 0;
                            FrameIndex = 0;
                            Texture = DeathAnimation;
                            Lives--;
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Updates pacman's points if pacman killed any ghosts
        /// this frame.
        /// </summary>
        public void SetGhostPoints()
        {
            int numOfAliveGhosts = 0;
            foreach (Ghost ghost in currentGame.GhostList)
            {
                if (ghost.Alive)
                {
                    numOfAliveGhosts++;
                }
            }
            ghostPoints = PointsPerDeadGhost * (currentGame.GhostList.Count - numOfAliveGhosts);
        }

        #endregion
    }
}
